/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package javax.servlet.jsp.tagext;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import java.io.Serializable;
import java.util.Enumeration;
import java.util.Hashtable;

/**
 * A base class for defining new tag handlers implementing Tag.
 * <p>
 * <p> The TagSupport class is a utility class intended to be used as
 * the base class for new tag handlers.  The TagSupport class
 * implements the Tag and IterationTag interfaces and adds additional
 * convenience methods including getter methods for the properties in
 * Tag.  TagSupport has one static method that is included to
 * facilitate coordination among cooperating tags.
 * <p>
 * <p> Many tag handlers will extend TagSupport and only redefine a
 * few methods.
 */
public class TagSupport implements IterationTag, Serializable {

	private static final long serialVersionUID = 1L;
	/**
	 * The value of the id attribute of this tag; or null.
	 */
	protected String id;
	/**
	 * The PageContext.
	 */
	protected transient PageContext pageContext;
	private Tag parent;
	private Hashtable<String, Object> values;

	/**
	 * Default constructor, all subclasses are required to define only
	 * a public constructor with the same signature, and to call the
	 * superclass constructor.
	 * <p>
	 * This constructor is called by the code generated by the JSP
	 * translator.
	 */
	public TagSupport() {
		// NOOP by default
	}

	// Actions related to body evaluation

	/**
	 * Find the instance of a given class type that is closest to a given
	 * instance.
	 * This method uses the getParent method from the Tag
	 * interface.
	 * This method is used for coordination among cooperating tags.
	 * <p>
	 * <p>
	 * The current version of the specification only provides one formal
	 * way of indicating the observable type of a tag handler: its
	 * tag handler implementation class, described in the tag-class
	 * subelement of the tag element.  This is extended in an
	 * informal manner by allowing the tag library author to
	 * indicate in the description subelement an observable type.
	 * The type should be a subtype of the tag handler implementation
	 * class or void.
	 * This additional constraint can be exploited by a
	 * specialized container that knows about that specific tag library,
	 * as in the case of the JSP standard tag library.
	 * <p>
	 * <p>
	 * When a tag library author provides information on the
	 * observable type of a tag handler, client programmatic code
	 * should adhere to that constraint.  Specifically, the Class
	 * passed to findAncestorWithClass should be a subtype of the
	 * observable type.
	 *
	 * @param from  The instance from where to start looking.
	 * @param klass The subclass of Tag or interface to be matched
	 * @return the nearest ancestor that implements the interface
	 * or is an instance of the class specified
	 */
	public static final Tag findAncestorWithClass(Tag from,
	                                              // TCK signature test fails with generics
	                                              @SuppressWarnings("rawtypes")
			                                              Class klass) {
		boolean isInterface = false;

		if (from == null ||
				klass == null ||
				(!Tag.class.isAssignableFrom(klass) &&
						!(isInterface = klass.isInterface()))) {
			return null;
		}

		for (; ; ) {
			Tag tag = from.getParent();

			if (tag == null) {
				return null;
			}

			if ((isInterface && klass.isInstance(tag)) ||
					((Class<?>) klass).isAssignableFrom(tag.getClass())) {
				return tag;
			}
			from = tag;
		}
	}

	/**
	 * Default processing of the start tag, returning SKIP_BODY.
	 *
	 * @return SKIP_BODY
	 * @throws JspException if an error occurs while processing this tag
	 * @see Tag#doStartTag()
	 */
	@Override
	public int doStartTag() throws JspException {
		return SKIP_BODY;
	}

	/**
	 * Default processing of the end tag returning EVAL_PAGE.
	 *
	 * @return EVAL_PAGE
	 * @throws JspException if an error occurs while processing this tag
	 * @see Tag#doEndTag()
	 */
	@Override
	public int doEndTag() throws JspException {
		return EVAL_PAGE;
	}

	/**
	 * Default processing for a body.
	 *
	 * @return SKIP_BODY
	 * @throws JspException if an error occurs while processing this tag
	 * @see IterationTag#doAfterBody()
	 */
	@Override
	public int doAfterBody() throws JspException {
		return SKIP_BODY;
	}

	/**
	 * Release state.
	 *
	 * @see Tag#release()
	 */
	@Override
	public void release() {
		parent = null;
		id = null;
		if (values != null) {
			values.clear();
		}
		values = null;
	}

	/**
	 * The Tag instance most closely enclosing this tag instance.
	 *
	 * @return the parent tag instance or null
	 * @see Tag#getParent()
	 */
	@Override
	public Tag getParent() {
		return parent;
	}

	/**
	 * Set the nesting tag of this tag.
	 *
	 * @param t The parent Tag.
	 * @see Tag#setParent(Tag)
	 */
	@Override
	public void setParent(Tag t) {
		parent = t;
	}

	/**
	 * The value of the id attribute of this tag; or null.
	 *
	 * @return the value of the id attribute, or null
	 */
	public String getId() {
		return id;
	}

	/**
	 * Set the id attribute for this tag.
	 *
	 * @param id The String for the id.
	 */
	public void setId(String id) {
		this.id = id;
	}

	/**
	 * Set the page context.
	 *
	 * @param pageContext The PageContext.
	 * @see Tag#setPageContext
	 */
	@Override
	public void setPageContext(PageContext pageContext) {
		this.pageContext = pageContext;
	}

	// private fields

	/**
	 * Associate a value with a String key.
	 *
	 * @param k The key String.
	 * @param o The value to associate.
	 */
	public void setValue(String k, Object o) {
		if (values == null) {
			values = new Hashtable<String, Object>();
		}
		values.put(k, o);
	}

	/**
	 * Get a the value associated with a key.
	 *
	 * @param k The string key.
	 * @return The value associated with the key, or null.
	 */
	public Object getValue(String k) {
		if (values == null) {
			return null;
		}
		return values.get(k);
	}

	/**
	 * Remove a value associated with a key.
	 *
	 * @param k The string key.
	 */
	public void removeValue(String k) {
		if (values != null) {
			values.remove(k);
		}
	}

	// protected fields

	/**
	 * Enumerate the keys for the values kept by this tag handler.
	 *
	 * @return An enumeration of all the keys for the values set,
	 * or null or an empty Enumeration if no values have been set.
	 */
	public Enumeration<String> getValues() {
		if (values == null) {
			return null;
		}
		return values.keys();
	}
}

